# python的 class 中有很多特殊用途的函数
# __str__函数，自定义打印字符串

class Student(object):
    def __init__(self, name):
        self.name = name
    
print(Student('rdn'))

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student Object named: %s' % self.name
    
print(Student('rdn'))

s = Student('rdn')

# 如果要对一个类进行 for 循环，可以实现__iter__和__next__函数
class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1

    def __iter__(self):
        return self
    
    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > 100000:
            raise StopIteration()
        return self.a
    
for n in Fib():
    print(n)

# 如果要实现像 list 那样取下标元素，需要实现__getitem_()方法
class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1

    def __iter__(self):
        return self
    
    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > 100000:
            raise StopIteration()
        return self.a
    
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a,b = b, a + b
        return a

print(Fib()[5]) 

# __getitem__函数不仅可以传入一个数值下标，也可以传入一个切片
# 改写fib函数，使其适应切片调用
class Fib(object):
    def __init__(self):
        self.b, self.b = 0, 1

    def __getitem__(self, n):
        if isinstance(n, int):
            a, b = 0, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice):
            start = n.start
            stop = n.stop
            if start == None:
                start = 0
            L = []
            a, b = 0, 1
            for x in range(stop):
                a, b = b, a + b
                if x >= start:
                    L.append(a)
            return L
        
f = Fib()

print(f[:5])
print(f[5:10])


# __getattr__函数
class Anmial(object):
    def __init__(self):
        self.name = 'Michael'

    def __getattr__(self, attr):
        if attr == 'score':
            return 100
        if attr == 'age':
            return lambda : 25
        raise AttributeError('\'Animal\' object has no attribute \'%s\'' % attr) # 调用不存在的属性时，__getattr__函数默认返回的是 None，当 attr 不存在时，可以抛出一个 AttributeError 错误
        
a = Anmial()
print(a.name)
print(a.score)
print(a.age())
try: 
    print(a.time) 
except:
    print("object has no attr")

# 链式调用实现路径拼接
class Chain(object):
    def __init__(self, path = ""):
        self._path = path

    def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path))
    
    def __str__(self):
        return self._path
    
print(Chain().status.users.items.list)

# 有些Rest API 会把参数放到 URL 中，比如：GET /users/:user/repos
# 实现一个带参数的链式调用
class Chain(object):
    def __init__(self, path = ""):
        self._path = path

    def __getattr__(self, path):
        if path == 'users':
            return lambda name : Chain(f"{self._path}/{path}/{name}")
        return Chain(f'{self._path}/{path}')
    def __str__(self):
        return self._path
    
print(Chain().status.users('Michael').repos)

# 通过__call__函数可以直接在实例本身上调用方法
class Student(object):
    def __init__(self, name = 'rdn'):
        self.name = name

    def __call__(self, age):
        print('My name is %s. I am %d years old.' % (self.name, age))

s = Student('Michael')
s(16)
# 对象和函数都可以直接通过自身调用
# 通过callable可以判断一个对象是否能被调用
print(callable('str'))
print(callable(s))
print(callable(Student()))